define(function (require, exports, module) {
    const EventEmitter = require('./events.js').onload();

    const syncMove = window.syncMaps

    function onload() {
        /**
         * @param {Object} a The first Mapbox GL Map
         * @param {Object} b The second Mapbox GL Map
         * @param {string|HTMLElement} container An HTML Element, or an element selector string for the compare container. It should be a wrapper around the two map Elements.
         * @param {Object} options
         * @param {string} [options.orientation=vertical] The orientation of the compare slider. `vertical` creates a vertical slider bar to compare one map on the left (map A) with another map on the right (map B). `horizontal` creates a horizontal slider bar to compare on mop on the top (map A) and another map on the bottom (map B).
         * @param {boolean} [options.mousemove=false] If `true` the compare slider will move with the cursor, otherwise the slider will need to be dragged to move.
         * @example
         * var compare = new mapboxgl.Compare(beforeMap, afterMap, '#wrapper', {
         *   orientation: 'vertical',
         *   mousemove: true
         * });
         * @see [Swipe between maps](https://www.mapbox.com/mapbox-gl-js/example/mapbox-gl-compare/)
         */
        function Compare(a, b, container, options) {
            this.options = options ? options : {};
            this._mapA = a;
            this._mapB = b;
            this._horizontal = this.options.orientation === 'horizontal';
            this._onDown = this._onDown.bind(this);
            this._onMove = this._onMove.bind(this);
            this._onMouseUp = this._onMouseUp.bind(this);
            this._onTouchEnd = this._onTouchEnd.bind(this);
            this._ev = new EventEmitter();
            this._swiper = document.createElement('div');
            this._swiper.className = this._horizontal ? 'compare-swiper-horizontal' : 'compare-swiper-vertical';

            this._controlContainer = document.createElement('div');
            this._controlContainer.className = this._horizontal ? 'mapboxgl-compare mapboxgl-compare-horizontal' : 'mapboxgl-compare';
            // this._controlContainer.className = this._controlContainer.className;
            this._controlContainer.appendChild(this._swiper);

            if (typeof container === 'string' && document.body.querySelectorAll) {
                // get container with a selector
                var appendTarget = document.body.querySelectorAll(container)[0];
                if (!appendTarget) {
                    throw new Error('Cannot find element with specified container selector.')
                }
                appendTarget.appendChild(this._controlContainer)
            } else if (container instanceof Element && container.appendChild) {
                // get container directly
                container.appendChild(this._controlContainer)
            } else {
                throw new Error('Invalid container specified. Must be CSS selector or HTML element.')
            }

            this._bounds = b.getContainer().getBoundingClientRect();
            var swiperPosition = (this._horizontal ? this._bounds.height : this._bounds.width) / 2;
            this._setPosition(swiperPosition);

            this._clearSync = syncMove(a, b);
            this._onResize = function () {
                this._bounds = b.getContainer().getBoundingClientRect();
                if (this.currentPosition) this._setPosition(this.currentPosition);
            }.bind(this);

            b.on('resize', this._onResize);

            if (this.options && this.options.mousemove) {
                a.getContainer().addEventListener('mousemove', this._onMove);
                b.getContainer().addEventListener('mousemove', this._onMove);
            }

            this._swiper.addEventListener('mousedown', this._onDown);
            this._swiper.addEventListener('touchstart', this._onDown);
        }

        Compare.prototype = {
            _setPointerEvents: function (v) {
                this._controlContainer.style.pointerEvents = v;
                this._swiper.style.pointerEvents = v;
            },

            _onDown: function (e) {
                if (e.touches) {
                    document.addEventListener('touchmove', this._onMove);
                    document.addEventListener('touchend', this._onTouchEnd);
                } else {
                    document.addEventListener('mousemove', this._onMove);
                    document.addEventListener('mouseup', this._onMouseUp);
                }
            },

            _setPosition: function (x) {
                x = Math.min(x, this._horizontal
                    ? this._bounds.height
                    : this._bounds.width);
                var pos = this._horizontal
                    ? 'translate(0, ' + x + 'px)'
                    : 'translate(' + x + 'px, 0)';
                this._controlContainer.style.transform = pos;
                this._controlContainer.style.WebkitTransform = pos;
                var clipA = this._horizontal
                    ? 'rect(0, 999em, ' + x + 'px, 0)'
                    : 'rect(0, ' + x + 'px, ' + this._bounds.height + 'px, 0)';
                var clipB = this._horizontal
                    ? 'rect(' + x + 'px, 999em, ' + this._bounds.height + 'px,0)'
                    : 'rect(0, 999em, ' + this._bounds.height + 'px,' + x + 'px)';

                this._mapA.getContainer().style.clip = clipA;
                this._mapB.getContainer().style.clip = clipB;
                this.currentPosition = x;
            },

            _onMove: function (e) {
                if (this.options && this.options.mousemove) {
                    this._setPointerEvents(e.touches ? 'auto' : 'none');
                }

                this._horizontal
                    ? this._setPosition(this._getY(e))
                    : this._setPosition(this._getX(e));
            },

            _onMouseUp: function () {
                document.removeEventListener('mousemove', this._onMove);
                document.removeEventListener('mouseup', this._onMouseUp);
                this.fire('slideend', {currentPosition: this.currentPosition});
            },

            _onTouchEnd: function () {
                document.removeEventListener('touchmove', this._onMove);
                document.removeEventListener('touchend', this._onTouchEnd);
                this.fire('slideend', {currentPosition: this.currentPosition});
            },

            _getX: function (e) {
                e = e.touches ? e.touches[0] : e;
                var x = e.clientX - this._bounds.left;
                // if (x < 0) x = 0;
                if (x < 0) x = e.clientX;
                if (x > this._bounds.width) x = this._bounds.width;
                return x;
            },

            _getY: function (e) {
                e = e.touches ? e.touches[0] : e;
                var y = e.clientY - this._bounds.top;
                if (y < 0) y = 0;
                if (y > this._bounds.height) y = this._bounds.height;
                return y;
            },

            /**
             * Set the position of the slider.
             *
             * @param {number} x Slider position in pixels from left/top.
             */
            setSlider: function (x) {
                this._setPosition(x);
            },

            /**
             * Adds a listener for events of a specified type.
             *
             * @param {string} type The event type to listen for; one of `slideend`.
             * @param {Function} listener The function to be called when the event is fired.
             * @returns {Compare} `this`
             */
            on: function (type, fn) {
                this._ev.on(type, fn);
                return this;
            },

            /**
             * Fire an event of a specified type.
             *
             * @param {string} type The event type to fire; one of `slideend`.
             * @param {Object} data Data passed to the event listener.
             * @returns {Compare} `this`
             */
            fire: function (type, data) {
                this._ev.emit(type, data);
                return this;
            },

            /**
             * Removes an event listener previously added with `Compare#on`.
             *
             * @param {string} type The event type previously used to install the listener.
             * @param {Function} listener The function previously installed as a listener.
             * @returns {Compare} `this`
             */
            off: function (type, fn) {
                this._ev.removeListener(type, fn);
                return this;
            },

            remove: function () {
                this._clearSync();
                this._mapB.off('resize', this._onResize);
                var aContainer = this._mapA.getContainer();

                if (!!aContainer) {
                    aContainer.style.clip = null;
                    aContainer.removeEventListener('mousemove', this._onMove);
                }

                var bContainer = this._mapB.getContainer();

                if (!!bContainer) {
                    bContainer.style.clip = null;
                    bContainer.removeEventListener('mousemove', this._onMove);
                }

                this._swiper.removeEventListener('mousedown', this._onDown);
                this._swiper.removeEventListener('touchstart', this._onDown);
                this._controlContainer.remove();
            }
        };

        return Compare
    }

    module.exports = {
        onload
    }
});
